using System;
using System.Collections.Generic;
using System.Text;

namespace Mathematics.Matrice
{
    public class Matrice
    {
        public bool symmetrical=false;
        private double[,] matriceArray;

        private int rowCount
        {
            get { return matriceArray.GetLength(0); }
        }

        private int colCount
        {
            get { return matriceArray.GetLength(1); }
        }

        public Matrice(double[,] array)
        {
            matriceArray = new double[array.GetLength(0), array.GetLength(1)];
            matriceArray = array;
        }

        public Matrice(int rows, int cols)
        {
            matriceArray = new double[rows, cols];
        }

        /// <summary>Gets the number of rows.</summary>
        public int RowCount
        {
            get { return matriceArray.GetLength(0); }
        }

        /// <summary>Gets the number of columns.</summary>
        public int ColCount
        {
            get { return matriceArray.GetLength(1); }
        }
        /// <summary>
        /// Transposes a matrice
        /// NOT YET TESTED!
        /// </summary>
        public void Transpose()
        {
            if (RowCount == ColCount)
            {
                // so we do not need to create a new array
                for (int i = 0; i < this.RowCount; i++)
                {
                    for (int j = i + 1; j < this.ColCount; j++)
                    {
                        double ij = matriceArray[i, j];
                        matriceArray[i, j] = matriceArray[j, i];
                        matriceArray[j, i] = ij;
                    }
                }
            }
            else
            {
                double[,] array = new double[ColCount, RowCount];
                for (int i = 0; i < this.RowCount; i++)
                {
                    for (int j = 0; j < this.ColCount; j++)
                    {
                        array[j, i] = matriceArray[i, j];
                    }
                }
                matriceArray = array;
            }
        }
        public Matrice TransposeNoChange()
        {
            double[,] array = new double[ColCount, RowCount];
            for (int i = 0; i < this.RowCount; i++)
            {
                for (int j = 0; j < this.ColCount; j++)
                {
                    array[j, i] = matriceArray[i, j];
                }
            }
            return new Matrice(array);
        }
        public Matrice GetDiagonal()
        {
            if (RowCount != ColCount)
            {
                throw new System.ArgumentException("Provided matrice is not a square matrice.");
            }
            Matrice array = new Matrice(rowCount, 1);
            for (int i = 0; i < this.rowCount; i++)
            {
                array[i, 0] = this[i, i];
            }
            return array;
        }
        /// <summary>
        /// Calculates the determinant of a matrice
        /// Tested works great...
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        private double Determinant(double[,] array)
        {
            double det = 0;
            if (RowCount != ColCount)
            {
                return 0;
            }
            else if (RowCount == 1)
            {
                return array[0, 0];
            }
            else if (RowCount == 2)
            {
                return array[0, 0] * array[1, 1] - array[0, 1] * array[1, 0];
            }
            // also sarrus should be added in here(3*3)
            else
            {
                double[,] dummyMatrice = new double[array.GetLength(0), array.GetLength(1)];
                for (int i = 0; i < ColCount; i++)
                {
                    int n = 0;
                    for (int j = 0; j < ColCount; j++)
                    {
                        if (j != i)
                        {
                            for (int k = 0; k < ColCount - 1; k++)
                            {
                                dummyMatrice[n, k] = array[j, k + 1];
                            }
                            n++;
                        }
                    }
                    if (i % 2 == 0)
                    {
                        det += array[i, 0] * Determinant(dummyMatrice);
                    }
                    else
                    {
                        det -= array[i, 0] * Determinant(dummyMatrice);
                    }
                }
                return det;
            }
        }

        public double Determinant()
        {
            return Determinant(this.matriceArray);
        }

        private Matrice Inverse(double[,] array)
        {
            Matrice inverse = new Matrice(array);
            if (RowCount != ColCount)
            {
                throw new System.ArgumentException("Provided matrice is not a square matrice.");
            }
            for (int k = 0; k < RowCount; ++k)
            {
                inverse[k, k] = -1 / inverse[k, k];
                for (int i = 0; i < RowCount; ++i)
                {
                    if (i != k)
                    {
                        inverse[i, k] *= inverse[k, k];
                    }
                }
                for (int i = 0; i < RowCount; ++i)
                {
                    if (i != k)
                    {
                        for (int j = 0; j < RowCount; ++j)
                        {
                            if (j != k)
                            {
                                inverse[i, j] += inverse[i, k] * inverse[k, j];
                            }
                        }
                    }
                }
                for (int i = 0; i < RowCount; ++i)
                {
                    if (i != k)
                    {
                        inverse[k, i] *= inverse[k, k];
                    }
                }
            }
            for (int i = 0; i < RowCount; ++i)
            {
                for (int j = 0; j < RowCount; ++j)
                {
                    inverse[i, j] = -inverse[i, j];
                }
            }
            return inverse;
        }

        public Matrice Inverse()
        {
            return Inverse(this.matriceArray);
        }

        private double Sum(double[,] array)
        {
            double sum = 0;
            foreach (double d in array)
            {
                sum += d;
            }
            return sum;
        }

        public double Sum()
        {
            return Sum(this.matriceArray);
        }

        public Matrice subMatrice(int startRow, int startCol, int rowCnt, int colCnt)
        {
            if (rowCount < startRow + rowCnt)
            {
                throw new System.ArgumentException("Matrice does not have enough rows.");
            }
            if (colCount < startCol + colCnt)
            {
                throw new System.ArgumentException("Matrice does not have enough cols.");
            }
            Matrice sub = new Matrice(rowCnt, colCnt);
            for (int i = 0, j = startRow; i < rowCnt; i++, j++)
            {
                for (int k = 0, l = startCol; k < colCnt; k++, l++)
                {
                    sub[i, k] = this.matriceArray[j, l];
                }
            }
            return sub;
        }

        public Matrice addColNew(int count)
        {
            double[,] newArray = new double[rowCount, colCount + count];
            for (int i = 0; i < rowCount; i++)
            {
                for (int j = 0; j < colCount; j++)
                {
                    newArray[i, j] = matriceArray[i, j];
                }
            }
            return new Matrice(newArray);
        }

        public void addCol(int count)
        {
            double[,] newArray = new double[rowCount, colCount + count];
            for (int i = 0; i < rowCount; i++)
            {
                for (int j = 0; j < colCount; j++)
                {
                    newArray[i, j] = matriceArray[i, j];
                }
            }
            this.matriceArray = newArray;
        }

        /// <summary>Gets or set the element indexed by <c>(i, j)</c>
        /// in the <c>Matrix</c>.</summary>
        /// <param name="i">Row index.</param>
        /// <param name="j">Column index.</param>
        public double this[int i, int j]
        {
            get { return matriceArray[i, j]; }
            set
            {
                if (symmetrical == false)
                    matriceArray[i, j] = value;
                else
                {
                    matriceArray[i, j] = value;
                    matriceArray[j, i] = value;
                }
            }
        }
        /// <summary>
        /// m1+m2 matrice
        /// </summary>
        /// <param name="m1"></param>
        /// <param name="m2"></param>
        /// <returns></returns>
        public static Matrice operator +(Matrice m1, Matrice m2)
        {
            if (m1.rowCount != m2.rowCount || m1.colCount != m2.colCount)
            {
                throw new System.ArgumentException("Matrice dimensions are not equal.");
            }
            else
            {
                Matrice sum = new Matrice(m1.rowCount, m1.colCount);
                for (int i = 0; i < m1.rowCount; i++)
                {
                    for (int j = 0; j < m1.colCount; j++)
                    {
                        sum[i, j] = m1[i, j] + m2[i, j];
                    }
                }
                return sum;
            }
        }

        /// <summary>
        /// m1-m2 matrice
        /// </summary>
        /// <param name="m1"></param>
        /// <param name="m2"></param>
        /// <returns></returns>
        public static Matrice operator -(Matrice m1, Matrice m2)
        {
            if (m1.rowCount != m2.rowCount || m1.colCount != m2.colCount)
            {
                throw new System.ArgumentException("Matrice dimensions are not equal.");
            }
            else
            {
                Matrice sum = new Matrice(m1.rowCount, m1.colCount);
                for (int i = 0; i < m1.rowCount; i++)
                {
                    for (int j = 0; j < m1.colCount; j++)
                    {
                        sum[i, j] = m1[i, j] - m2[i, j];
                    }
                }
                return sum;
            }
        }

        /// <summary>
        /// m1*s1 matrice (where s1 is a scalar value)
        /// </summary>
        /// <param name="m1"></param>
        /// <param name="s1"></param>
        /// <returns></returns>
        public static Matrice operator *(Matrice m1, double s1)
        {
            Matrice mul = new Matrice(m1.rowCount, m1.colCount);
            for (int i = 0; i < m1.rowCount; i++)
            {
                for (int j = 0; j < m1.colCount; j++)
                {
                    mul[i, j] = m1[i, j] * s1;
                }
            }
            return mul;
        }

        /// <summary>
        /// m1*m2 matrice
        /// </summary>
        /// <param name="m1">Operand 1</param>
        /// <param name="m2">Operand 2</param>
        /// <returns>New matrice</returns>
        /// <remarks>
        /// Some error reporting should do better when using library
        /// </remarks>
        public static Matrice operator *(Matrice m1, Matrice m2)
        {
            if (m2.rowCount != m1.colCount)
            {
                throw new System.ArgumentException("Matrice inner dimensions are not equal.");
            }
            else
            {
                Matrice mul = new Matrice(m1.rowCount, m2.colCount);
                for (int j = 0; j < m2.colCount; j++)
                {
                    for (int i = 0; i < m1.rowCount; i++)
                    {
                        double s = 0;
                        for (int k = 0; k < m1.colCount; k++)
                        {
                            s += m1[i, k] * m2[k, j];
                        }
                        mul[i, j] = s;
                    }
                }
                return mul;
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < rowCount; i++)
            {
                if (i == 0)
                    sb.Append("[[");
                else
                    sb.Append(" [");
                for (int j = 0; j < colCount; j++)
                {
                    if (j != 0)
                        sb.Append(',');
                    sb.Append(this[i, j]);
                }
                if (i == rowCount - 1)
                    sb.Append("]]");
                else
                    sb.Append("]\n");
            }
            return sb.ToString();
        }
    }
}
